/** @file m_plane.cpp
	@brief 3D planes */

#include "math/m_plane.h"

using namespace G3;

//==============================================================================
// Constructors
//==============================================================================
template <class T>
G3::PLANE3<T>::PLANE3 (T A, T B, T C, T D) {
    n = VEC3<T> (A, B, C);

	if (n != VEC3<T> (0, 0, 0))
	    n.Normalize ();

    d = D;
}

template <class T>
G3::PLANE3<T>::PLANE3 (const VEC3<T> &a, const VEC3<T> &b, const VEC3<T> &c) {
    n = CrossProduct (b - a, c - a);

	if (n != VEC3<T> (0, 0, 0))
	    n.Normalize ();

    p = (a + b + c) / 3;
    d = -(n * p);
}

template <class T>
G3::PLANE3<T>::PLANE3 (const VEC3<T> &N, const VEC3<T> &P) {
    n = N;

	if (n != VEC3<T> (0, 0, 0))
	    n.Normalize ();

    p = P;
    d = -(N * P);
}

//==============================================================================
// = operators
//==============================================================================
template <class T>
AXIS2_3<T>& G3::AXIS2_3<T>::operator= (const AXIS2_3<T> &A) {
	u = A.u;
	v = A.v;

	return *this;
}

template <class T>
PLANE3<T>& G3::PLANE3<T>::operator= (const PLANE3<T> &P) {
	n = P.n;
	p = P.p;
	d = P.d;

	return *this;
}

//==============================================================================
// ::Normalize ()
//==============================================================================
template <class T>
void G3::PLANE3<T>::Normalize () {
    d /= (T) n.GetLength ();
    n.Normalize ();
}

//==============================================================================
// Where's the point? Behind the plane?
//==============================================================================
template <class T>
HALFSPACE G3::PLANE3<T>::ClassifyPoint (const VEC3<T> &P) const {
    double dd = (double) GetDistance (P);

	//if (dd == 0) return ON_PLANE;
	if ((dd > -G3_EPSILON) && (dd < G3_EPSILON))
		return ON_PLANE;
    //if (dd < 0) return NEGATIVE;
    if (dd < G3_EPSILON)
		return NEGATIVE;
    return POSITIVE;
}

//==============================================================================
// Gets the distance of a point from the plane
//==============================================================================
template <class T>
T G3::PLANE3<T>::GetDistance (const VEC3<T> &P) const {
    return (n * P + d);
}

//==============================================================================
// Calculates the plane's surface axis
//==============================================================================
template <class T>
AXIS2_3<T> G3::PLANE3<T>::CalcSurfaceAxis () const {
	AXIS2_3<T> uv;

	if (n.x != 0 && n.z != 0)
		uv.u = VEC3<T> (d / n.x, 0, - d / n.z);
	else if (n.x == 0 && n.z != 0)
		uv.u = VEC3<T> (-1/*G3_GETSIGN (n.z)*/, 0, 0);
	else if (n.x != 0 && n.z == 0)
		uv.u = VEC3<T> (0, 0, 1/*G3_GETSIGN (n.x)*/);
	else if (n.x == 0 && n.z == 0 && n.y != 0)
		uv.u = VEC3<T> (-1/*G3_GETSIGN (n.y)*/, 0, 0);
	else
		return uv;

	uv.v = CrossProduct (n, uv.u);

	// This is here because of some .MAP editors
	if ((n.x == 0 && n.z < 0) ||
		(n.x < 0 && n.z == 0) ||
		(n.x == 0 && n.z == 0 && n.y != 0)) {

		uv.v = -uv.v;
	}

	if (uv.u != VEC3<T> (0, 0, 0))
		uv.u.Normalize ();
	if (uv.v != VEC3<T> (0, 0, 0))
		uv.v.Normalize ();

	return uv;
}

template <class T>
void G3::PLANE3<T>::Render (COL3F Color, double Scale) const {
    bool Lighting = false;
    bool Texture = false;

    glPushMatrix ();
    if (glIsEnabled (GL_TEXTURE_2D)){ glDisable (GL_TEXTURE_2D); Texture = true; }
    if (glIsEnabled (GL_LIGHTING)){ glDisable (GL_LIGHTING); Lighting = true; }

    glEnable (GL_LINE_SMOOTH);
    glLineWidth (2.0f);

	// Normal
	glColor3f (0.1f, 0.1f, 1.0f);
	glBegin (GL_LINES);
		glVertex3d (p.x, p.y, p.z);
		glVertex3d (p.x + n.x * Scale, p.y + n.y * Scale, p.z + n.z * Scale);
	glEnd ();

	AXIS2_3<T> axis = CalcSurfaceAxis ();

	// U Axis -R
	glColor3f (1.0f, 0.1f, 0.1f);
	glBegin (GL_LINES);
		glVertex3d (p.x, p.y, p.z);
		glVertex3d (p.x + axis.u.x * Scale, p.y + axis.u.y * Scale, p.z + axis.u.z * Scale);
	glEnd ();

	// V Axis -G
	glColor3f (0.1f, 1.0f, 0.1f);
	glBegin (GL_LINES);
		glVertex3d (p.x, p.y, p.z);
		glVertex3d (p.x + axis.v.x * Scale, p.y + axis.v.y * Scale, p.z + axis.v.z * Scale);
	glEnd ();

    glLineWidth (1.0f);
    glDisable (GL_LINE_SMOOTH);

    if (Texture) { glEnable (GL_TEXTURE_2D); }
    if (Lighting) { glEnable (GL_LIGHTING); }
    glPopMatrix ();
}

//==============================================================================
// Checks to see if these two planes are parallel
//==============================================================================
template <class T>
__inline bool G3::Parallel (const PLANE3<T> &Plane1, const PLANE3<T> &Plane2) {
	T k = Plane1.n.x / Plane2.n.x;

	return (Plane1.n.y / Plane2.n.y == k && Plane1.n.z / Plane2.n.z == k);
}

//==============================================================================
// Gets the distance between two parallel planes
//
//	Returns -1.0f when the two planes are not parallel
//==============================================================================
template <class T>
__inline T G3::GetDistance (const PLANE3<T> &Plane1, const PLANE3<T> &Plane2) {
	T k = Plane1.n.x / Plane2.n.x;

	if (Plane1.n.y / Plane2.n.y == k &&
		Plane1.n.z / Plane2.n.z == k) {

		return Plane2.GetDistance (Plane1.p);
	}
	return -1;
}

//==============================================================================
// Projects a vector onto a plane
//==============================================================================
template <class T>
VEC2<T> G3::ProjectRelativeToPlane (const PLANE3<T> &Plane, const VEC3<T> &P) {
	AXIS2_3<T> uv = Plane.CalcSurfaceAxis ();
	VEC3<T> Pv;

	Pv = ProjectOntoPlane (Plane, P);

	return VEC2<T> (Pv * uv.u, Pv * uv.v);
}

//==============================================================================
// Explicit template instantiation
//==============================================================================
template class G3::AXIS2_3<int>;
template class G3::AXIS2_3<float>;
template class G3::AXIS2_3<double>;

template class G3::PLANE3<int>;
template class G3::PLANE3<float>;
template class G3::PLANE3<double>;

template __inline bool G3::Parallel (const PLANE3<int> &Plane1, const PLANE3<int> &Plane2);
template __inline bool G3::Parallel (const PLANE3<float> &Plane1, const PLANE3<float> &Plane2);
template __inline bool G3::Parallel (const PLANE3<double> &Plane1, const PLANE3<double> &Plane2);

template __inline int G3::GetDistance (const PLANE3<int> &Plane1, const PLANE3<int> &Plane2);
template __inline float G3::GetDistance (const PLANE3<float> &Plane1, const PLANE3<float> &Plane2);
template __inline double G3::GetDistance (const PLANE3<double> &Plane1, const PLANE3<double> &Plane2);

template VEC2<int> G3::ProjectRelativeToPlane (const PLANE3<int> &Plane, const VEC3<int> &P);
template VEC2<float> G3::ProjectRelativeToPlane (const PLANE3<float> &Plane, const VEC3<float> &P);
template VEC2<double> G3::ProjectRelativeToPlane (const PLANE3<double> &Plane, const VEC3<double> &P);
